/*
 * License GNU LGPL
 * Copyright (C) 2012 Amrullah <amrullah@panemu.com>.
 */
package com.abc.cheque.control;


import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.SortType;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.HBoxBuilder;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;

import org.apache.commons.beanutils.PropertyUtils;

import com.abc.cheque.common.TableCriteria;
import com.abc.cheque.common.TableData;
import com.abc.cheque.common.TiwulFXUtil;
import com.abc.cheque.ui.table.BaseColumn;
import com.abc.cheque.ui.table.CheckBoxColumn;
import com.abc.cheque.ui.table.DateColumn;
import com.abc.cheque.ui.table.NumberColumn;
import com.abc.cheque.ui.table.TableControl;
import com.abc.cheque.ui.table.TableControl.Component;
import com.abc.cheque.ui.table.TableController;
import com.abc.cheque.ui.table.TextColumn;

/**
 * Class that serves data loading for LookupField.
 * @author Amrullah <amrullah@panemu.com>
 */
public abstract class LookupFieldController<T> {

    private LookupWindow lookupWindow;
    private Stage dialogStage;
    private Class<T> recordClass;

    /**
     * 
     * @param recordClass The class of record/object/POJO that will be displayed in table's row
     */
    public LookupFieldController(Class<T> recordClass) {
        this.recordClass = recordClass;
    }

    public abstract String[] getColumns();

    /**
     * This method is published to serve data displayed on Lookup's suggestion list.
     * Eventually it will call {@link #loadData(int, java.util.List, java.util.List, java.util.List, int)}
     * @param propertyName
     * @param key
     * @return 
     */
    public List<T> loadDataForPopup(String propertyName, String key) {
        return loadDataForPopup(propertyName, key, TableCriteria.Operator.ilike_anywhere);
    }

    /**
     * This method is published to serve data displayed on Lookup's suggestion list.
     * Eventually it will call {@link #loadData(int, java.util.List, java.util.List, java.util.List, int)}
     * @param propertyName
     * @param key
     * @param operator
     * @return 
     */
    public List<T> loadDataForPopup(String propertyName, String key, TableCriteria.Operator operator) {
        List<TableCriteria> lstCriteria = new ArrayList<>();
        if (key != null && !key.isEmpty()) {
            lstCriteria.add(new TableCriteria(propertyName, operator, key));
        }
        TableData data = loadData(0, lstCriteria, Arrays.asList(propertyName), Arrays.asList(SortType.ASCENDING), TiwulFXUtil.DEFAULT_LOOKUP_SUGGESTION_ITEMS);
        return (List<T>) data.getRows();
    }
    
    /**
     * 
     * @return 
     */
    protected String getWindowTitle() {
        return TiwulFXUtil.getLiteral("lookup.title");
    }

    public T show(Window stage, T initialValue, String propertyName) {
        return show(stage, initialValue, propertyName, null);
    }

    /**
     * Show lookup dialog.
     * @param stage parent
     * @param initialValue this value will be returned if user clik the close button instead of double clicking a row or click Select button
     * @param propertyName propertyName corresponds to searchCriteria
     * @param searchCriteria searchCriteria (nullable)
     * @return selected object or the initialValue
     */
    public T show(Window stage, T initialValue, String propertyName, String searchCriteria) {
        if (dialogStage == null) {
            PropertyDescriptor[] props = PropertyUtils.getPropertyDescriptors(recordClass);
            lookupWindow = new LookupWindow();
            for (String clm : getColumns()) {
                for (PropertyDescriptor prop : props) {
                    if (prop.getName().equals(clm)) {
                        Class type = prop.getPropertyType();
                        if (type.equals(Boolean.class)) {
                            lookupWindow.table.addColumn(new CheckBoxColumn<T>(clm));
                        } else if (type.equals(String.class)) {
                            lookupWindow.table.addColumn(new TextColumn<T>(clm));
                        } else if (type.equals(Date.class)) {
                            lookupWindow.table.addColumn(new DateColumn<T>(clm));
                        } else if (Number.class.isAssignableFrom(type)) {

                            if (Long.class.isAssignableFrom(type)) {
                                lookupWindow.table.addColumn(new NumberColumn<T, Long>(clm, type));
                            } else {
                                lookupWindow.table.addColumn(new NumberColumn<T, Double>(clm, type));
                            }
                        } else {
                            TableColumn column = new TableColumn();
                            column.setCellValueFactory(new PropertyValueFactory(clm));
                            lookupWindow.table.addColumn(column);
                        }
                        break;
                    }
                }

            }
            dialogStage = new Stage();
            dialogStage.initOwner(stage);
            dialogStage.initStyle(StageStyle.UTILITY);
            dialogStage.setResizable(true);
            dialogStage.initModality(Modality.WINDOW_MODAL);
            dialogStage.setScene(new Scene(lookupWindow));
            dialogStage.getIcons().add(new Image(LookupFieldController.class.getResourceAsStream("/images/lookup.png")));
            dialogStage.setTitle(getWindowTitle());
            dialogStage.getScene().getStylesheets().add("tiwulfx.css");
            initCallback(lookupWindow, lookupWindow.table);
        }

        for (TableColumn column : lookupWindow.table.getTableView().getColumns()) {
            if (column instanceof BaseColumn && ((BaseColumn) column).getPropertyName().equals(propertyName)) {
                if (searchCriteria != null && !searchCriteria.isEmpty()) {
                    TableCriteria tc = new TableCriteria(propertyName, TableCriteria.Operator.ilike_anywhere, searchCriteria);
                    ((BaseColumn) column).setTableCriteria(tc);
                } else {
                    ((BaseColumn) column).setTableCriteria(null);
                }

                break;
            }
        }
        selectedValue = initialValue;
        beforeShowCallback(lookupWindow.table);
        lookupWindow.table.reloadFirstPage();
        dialogStage.showAndWait();
        return selectedValue;
    }
    
    /**
     * Override this method to get reference on Lookup container and TableControl object.
     * This is useful to set preferred size of lookup window and set particular column's width.
     * This callback is executed after Lookup Stage along with TableControl are ready to be shown.
     * It is only executed one time. The next lookup dialog show will not be called.
     * @param container
     * @param table 
     * @see #beforeShowCallback(com.panemu.tiwulfx.table.TableControl)
     */
    protected void initCallback(VBox container, TableControl<T> table) {
    }
    
    /**
     * Call back that is called every time the lookup dialog is about to be shown.
     * @param table 
     * @see #initCallback(javafx.scene.layout.VBox, com.panemu.tiwulfx.table.TableControl)
     */
    protected void beforeShowCallback(TableControl<T> table) {
        
    }
    
    private T selectedValue;

    private void select() {
        selectedValue = lookupWindow.table.getSelectedItem();
        close();

    }
    
    private void close() {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                dialogStage.hide();
            }
        });
    }

    protected abstract TableData loadData(int startIndex, List<TableCriteria> filteredColumns, List<String> sortedColumns, List<SortType> sortingTypes, int maxResult);

    private class LookupTableController extends TableController<T> {

        @Override
        public TableData loadData(int startIndex, List<TableCriteria> filteredColumns, List<String> sortedColumns, List<SortType> sortingTypes, int maxResult) {
            return LookupFieldController.this.loadData(startIndex, filteredColumns, sortedColumns, sortingTypes, maxResult);
        }

        @Override
        public void doubleClick(T record) {
            select();
        }
    }

    private class LookupWindow extends VBox {

        TableControl<T> table = new TableControl<>();
        private Button button = new Button(TiwulFXUtil.getLiteral("lookup.select"));

        public LookupWindow() {
            addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {

                @Override
                public void handle(KeyEvent event) {
                    if (event.getCode() == KeyCode.ESCAPE) {
                        close();
                    }
                }
            });
            HBox pnlButton = HBoxBuilder.create().alignment(Pos.CENTER).padding(new Insets(10)).children(button).build();
            getChildren().addAll(table, pnlButton);
            table.setController(new LookupTableController());
            table.setVisibleComponents(false, Component.BUTTON_DELETE,
                    Component.BUTTON_EDIT,
                    Component.BUTTON_EXPORT,
                    Component.BUTTON_INSERT,
                    Component.BUTTON_SAVE);
            button.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent t) {
                    select();
                }
            });
        }
    }
}
